/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package me.longerian.abcandroid.pinnedheaderlist;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.ListView;

/**
 * A ListView that maintains a header pinned at the top of the list. The pinned
 * header can be pushed up and dissolved as needed.
 */
public class PinnedHeaderListView extends ListView {

	/**
	 * Adapter interface. The list adapter must implement this interface.
	 */
	public interface PinnedHeaderAdapter {

		/**
		 * Pinned header state: don't show the header.
		 */
		public static final int PINNED_HEADER_GONE = 0;

		/**
		 * Pinned header state: show the header at the top of the list.
		 */
		public static final int PINNED_HEADER_VISIBLE = 1;

		/**
		 * Pinned header state: show the header. If the header extends beyond
		 * the bottom of the first shown element, push it up and clip.
		 */
		public static final int PINNED_HEADER_PUSHED_UP = 2;

		/**
		 * Computes the desired state of the pinned header for the given
		 * position of the first visible list item. Allowed return values are
		 * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or
		 * {@link #PINNED_HEADER_PUSHED_UP}.
		 */
		int getPinnedHeaderState(int position);

		/**
		 * Configures the pinned header view to match the first visible list
		 * item.
		 * 
		 * @param header
		 *            pinned header view.
		 * @param position
		 *            position of the first visible list item.
		 * @param alpha
		 *            fading of the header view, between 0 and 255.
		 */
		void configurePinnedHeader(View header, int position, int alpha);
		
		void configureNestedHeader(ViewGroup nestedHeader, View pinnedHeader);
		
	}
	
	private static final int MAX_ALPHA = 255;

	private PinnedHeaderAdapter mAdapter;
	private View mPinnedHeaderView;
	private boolean mPinnedHeaderViewVisible;

	private int mPinnedHeaderViewWidth;

	private int mPinnedHeaderViewHeight;
	
	private ViewGroup mNestedHeaderView;
	private int mNestedHeaderViewHeight;
	
	public PinnedHeaderListView(Context context) {
		super(context);
	}

	public PinnedHeaderListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public PinnedHeaderListView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
	}

	public void setPinnedHeaderView(View view) {
		mPinnedHeaderView = view;

		// Disable vertical fading when the pinned header is present
		// TODO change ListView to allow separate measures for top and bottom
		// fading edge;
		// in this particular case we would like to disable the top, but not the
		// bottom edge.
		if (mPinnedHeaderView != null) {
			setFadingEdgeLength(0);
			mPinnedHeaderViewHeight = mPinnedHeaderView.getHeight();
		}
		requestLayout();
	}
	
	/* (non-Javadoc)
	 * @see android.widget.ListView#addHeaderView(android.view.View, java.lang.Object, boolean)
	 */
	public void setNestedHeaderView(ViewGroup view, Object data, boolean isSelectable) {
		if(view != null) {
			addHeaderView(view, data, isSelectable);
			mNestedHeaderView = view;
			mNestedHeaderViewHeight = view.getHeight();
		}
	}

	@Override
	public void setAdapter(ListAdapter adapter) {
		super.setAdapter(adapter);
		mAdapter = (PinnedHeaderAdapter) adapter;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		
		if (mPinnedHeaderView != null) {
			measureChild(mPinnedHeaderView, widthMeasureSpec, heightMeasureSpec);
			mPinnedHeaderViewWidth = mPinnedHeaderView.getMeasuredWidth();
			mPinnedHeaderViewHeight = mPinnedHeaderView.getMeasuredHeight();
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		
		
		if (mPinnedHeaderView != null) {
			mPinnedHeaderView.layout(0, 0, mPinnedHeaderViewWidth, mPinnedHeaderViewHeight);
			configureHeaderView(getFirstVisiblePosition());
		}
	}
	
	private int getPinnedHeaderState(int position) {
		int state = PinnedHeaderAdapter.PINNED_HEADER_GONE;
		if(position == 0) {
			int nestedHeaderBottom = mNestedHeaderView.getBottom();
			if(nestedHeaderBottom > mPinnedHeaderViewHeight) {
				state = PinnedHeaderAdapter.PINNED_HEADER_GONE; 
			} else {
				state = PinnedHeaderAdapter.PINNED_HEADER_VISIBLE;
			}
		} else {
			state = PinnedHeaderAdapter.PINNED_HEADER_VISIBLE;
		}
		return state;
	}

	public void configureHeaderView(int position) {
		if (mPinnedHeaderView == null) {
			return;
		}
		int state = getPinnedHeaderState(position);
//		int state = mAdapter.getPinnedHeaderState(position);
		switch (state) {
		case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
			mAdapter.configureNestedHeader(mNestedHeaderView, mPinnedHeaderView);
			mPinnedHeaderViewVisible = false;
			if(mPinnedHeaderView != null) {
				mPinnedHeaderView.setVisibility(View.GONE);
			}
			break;
		}

		case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
			mAdapter.configurePinnedHeader(mPinnedHeaderView, position, MAX_ALPHA);
			mAdapter.configureNestedHeader(mNestedHeaderView, mPinnedHeaderView);
			if (mPinnedHeaderView.getTop() != 0) {
				mPinnedHeaderView.layout(0, 0, mPinnedHeaderViewWidth, mPinnedHeaderViewHeight);
			}
			mPinnedHeaderViewVisible = false;
			if(mPinnedHeaderView != null) {
				mPinnedHeaderView.setVisibility(View.VISIBLE);
			}
			break;
		}

		case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
			View firstView = getChildAt(0);
			int bottom = firstView.getBottom();
			// int itemHeight = firstView.getHeight();
			int headerHeight = mPinnedHeaderView.getHeight();
			int y;
			int alpha;
			if (bottom < headerHeight) {
				y = (bottom - headerHeight);
				alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
			} else {
				y = 0;
				alpha = MAX_ALPHA;
			}
			mAdapter.configurePinnedHeader(mPinnedHeaderView, position, alpha);
			if (mPinnedHeaderView.getTop() != y) {
				mPinnedHeaderView.layout(0, y, mPinnedHeaderViewWidth, mPinnedHeaderViewHeight
						+ y);
			}
			mPinnedHeaderViewVisible = false;
			break;
		}
		}
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);
		if (mPinnedHeaderViewVisible) {
			drawChild(canvas, mPinnedHeaderView, getDrawingTime());
		}
	}
}
